package org.lboot.llog.aspect;

import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import io.swagger.annotations.ApiOperation;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.lboot.core.auth.AuthService;
import org.lboot.llog.anno.ApiLog;
import org.lboot.llog.constant.MethodTypeConst;
import org.lboot.llog.domain.ApiLogDTO;
import org.lboot.llog.event.ApiLogCreatedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 对注解进行切面
 */
@Slf4j
@Aspect
@Component
public class ApiLogAspect {

    @Resource
    AuthService authService;

    @Resource
    ApplicationContext context;


    /**
     * 以自定义注解作为切入
     */
    @Pointcut("@annotation(org.lboot.llog.anno.ApiLog)")
    public void ApiLog(){}


    /**
     * 切面
     * @param joinPoint 切面信息
     */
    @Around("ApiLog()")
    public Object handleApiLog(ProceedingJoinPoint joinPoint) throws Throwable {
        return recordApiLog(joinPoint);
    }

    @SneakyThrows
    public Object recordApiLog(ProceedingJoinPoint joinPoint) throws Throwable {
        // 初始化日志类
        ApiLogDTO dto = new ApiLogDTO();
        // 获取注解
        Signature signature = joinPoint.getSignature();

        MethodSignature methodSignature = (MethodSignature) signature;

        Method method = methodSignature.getMethod();
        // 获取自定义注解信息
        ApiLog apiLog = method.getAnnotation(ApiLog.class);
        // 获取@Swagger文档注解
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        // 判断是否为空
        if (Validator.isNotEmpty(apiOperation)){
            String apiName = apiOperation.value();
            dto.setApiName(apiName);
        }
        Boolean isLogin = authService.isLogin();
        // 根据登录态处理
        if (isLogin){
            dto.setOperatorId(authService.getUid());
        }else {
            dto.setOperatorId("0");
        }
        // 获取模块信息
        String module = apiLog.module();
        if (Validator.isEmpty(module)){
            module = apiLog.value();
        }
        dto.setModule(module);
        // 获取类与方法信息
        Object target = joinPoint.getTarget();
        String className = target.getClass().getName();
        String methodName = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes()).getName();
        dto.setClazz(className);
        dto.setMethod(methodName);
        // 获取请求IP
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String reqMethod = request.getMethod();
        // 获取追踪ID --> 多种命名方式匹配
        String traceId = request.getHeader("trace_id");
        if (Validator.isEmpty(traceId)){
            traceId = request.getHeader("trace-id");
        }
        if (Validator.isEmpty(traceId)){
            traceId = request.getHeader("traceId");
        }
        if (Validator.isEmpty(traceId)){
            // 如果请求头没有携带，则自己设置追踪ID
            traceId = IdUtil.randomUUID();
        }
        dto.setTraceId(traceId);
        request.setAttribute("trace-id", traceId);
        request.setAttribute("traceId", traceId);
        request.setAttribute("trace_id", traceId);
        // 获取操作信息
        switch (reqMethod){
            case "GET":{
                dto.setAction(MethodTypeConst.READ);
                break;
            }
            case "POST":{
                dto.setAction(MethodTypeConst.CREATE);
                break;
            }
            case "PUT":{
                dto.setAction(MethodTypeConst.UPDATE);
                break;
            }
            case "DELETE":{
                dto.setAction(MethodTypeConst.DELETE);
                break;
            }
        }
        String clientIp = ServletUtil.getClientIP(request);
        dto.setClientIp(clientIp);
        // 设置请求参数
        //获取入参
        Object[] objs = joinPoint.getArgs();
        String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames(); // 参数名
        Map<String, Object> paramMap = new HashMap<String, Object>();
        for (int i = 0; i < objs.length; i++) {
            paramMap.put(argNames[i], objs[i]);
        }
        dto.setParams(JSONUtil.toJsonStr(paramMap));
        // 设置开始时间
        long startTime = System.currentTimeMillis();
        dto.setStartTime(new Date());
        try {
            Object object = joinPoint.proceed();
            dto.setStatus(1);
            dto.setEndTime(new Date());
            dto.setTotalTime((int) (System.currentTimeMillis() - startTime));
            // 返回值
            if (null != object) {
                String returnData = JSONUtil.toJsonStr(object);
                dto.setReturnData(returnData);
            }
            return object;
        } catch (Throwable e) {
            dto.setStatus(0);
            dto.setEndTime(new Date());
            dto.setTotalTime((int) (System.currentTimeMillis() - startTime));
            dto.setReturnData(e.getMessage());
            throw e;
        } finally {
            int ignoreRequest = apiLog.ignoreRequest();
            // 是否忽略请求入参
            if (ignoreRequest == 1){
                dto.setParams("***");
            }
            // 是否忽略返回参数
            int ignoreResponse = apiLog.ignoreResponse();
            if (ignoreResponse == 1){
                dto.setReturnData("***");
            }
            // 发布日志事件
            context.publishEvent(new ApiLogCreatedEvent(this,dto));
        }
    }
}
